//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <gmock/gmock.h>

#include <sqbind/sqbBind.h>
#include <sqbind/sqbClassTypeTag.h>

#include "fixtures/TypeFixture.h"
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
typedef SquirrelFixture BindClassFunctionTest;
typedef SquirrelFixture BindNativeClassFunctionTest;
typedef SquirrelFixture BindSingletonFunctionTest;
typedef SquirrelFixture BindNativeSingletonFunctionTest;

//----------------------------------------------------------------------------------------------------------------------
class BaseWithFunction
{
public:
  MOCK_METHOD0(BaseFunction, void());
  MOCK_METHOD1(BaseNativeFunction, SQInteger(HSQUIRRELVM));
};

SQBIND_DECLARE_NON_COPYABLE_CLASS(BaseWithFunction);

//----------------------------------------------------------------------------------------------------------------------
class DerivedWithFunctionNoOffset : public BaseWithFunction
{
public:

  MOCK_METHOD0(DerivedFunction, void());
  MOCK_METHOD1(DerivedNativeFunction, SQInteger(HSQUIRRELVM));
};

SQBIND_DECLARE_NON_COPYABLE_CLASS(DerivedWithFunctionNoOffset);

//----------------------------------------------------------------------------------------------------------------------
class DerivedWithFunctionWithOffset : public BaseWithFunction
{
public:

  MOCK_METHOD0(DerivedFunction, void());
  MOCK_METHOD1(DerivedNativeFunction, SQInteger(HSQUIRRELVM));

  // this causes derived to be offset from base
  //
  virtual ~DerivedWithFunctionWithOffset() {}
};

SQBIND_DECLARE_NON_COPYABLE_CLASS(DerivedWithFunctionWithOffset);

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindClassFunctionTest, TestInheritance)
{
  ::testing::StrictMock<BaseWithFunction> base;
  ::testing::StrictMock<DerivedWithFunctionNoOffset> derivedNoOffset;
  ::testing::StrictMock<DerivedWithFunctionWithOffset> derivedWithOffset;

  ASSERT_EQ(0, CLASS_OFFSET(BaseWithFunction, DerivedWithFunctionNoOffset));
  ASSERT_NE(0, CLASS_OFFSET(BaseWithFunction, DerivedWithFunctionWithOffset));

  EXPECT_CALL(base, BaseFunction())
    .Times(1);

  EXPECT_CALL(derivedNoOffset, BaseFunction())
    .Times(1);
  EXPECT_CALL(derivedNoOffset, DerivedFunction())
    .Times(1);

  EXPECT_CALL(derivedWithOffset, BaseFunction())
    .Times(1);
  EXPECT_CALL(derivedWithOffset, DerivedFunction())
    .Times(1);

  sq_pushroottable(m_vm);

  bool result = sqb::Bind::BindClass<BaseWithFunction, sqb::NoBaseClass>(m_vm, -1, _SC("BaseWithFunction"));
  ASSERT_TRUE(result) << "sqb::Bind::BindClass<BaseWithFunction, sqb::NoBaseClass>";

  sq_pushstring(m_vm, _SC("BaseWithFunction"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawget(m_vm, -2));
  ASSERT_TRUE(sqb::Bind::BindClassFunction<BaseWithFunction>(m_vm, -1, &BaseWithFunction::BaseFunction, _SC("BaseFunction")));

  // bind base to a slot in the root table
  //
  sq_pushstring(m_vm, _SC("baseWithFunction"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_createinstance(m_vm, -2));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_setinstanceup(m_vm, -1, static_cast<BaseWithFunction *>(&base)));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -4));

  // pop BaseWithFunction class
  //
  sq_poptop(m_vm);

  result = sqb::Bind::BindClass<DerivedWithFunctionNoOffset, BaseWithFunction>(m_vm, -1, _SC("DerivedWithFunctionNoOffset"));
  ASSERT_TRUE(result) << "sqb::Bind::BindClass<DerivedWithFunctionNoOffset, BaseWithFunction>";

  sq_pushstring(m_vm, _SC("DerivedWithFunctionNoOffset"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawget(m_vm, -2));
  ASSERT_TRUE(sqb::Bind::BindClassFunction<DerivedWithFunctionNoOffset>(m_vm, -1, &DerivedWithFunctionNoOffset::DerivedFunction, _SC("DerivedFunction")));

  // bind derivedNoOffset to a slot in the root table
  //
  sq_pushstring(m_vm, _SC("derivedNoOffset"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_createinstance(m_vm, -2));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_setinstanceup(m_vm, -1, static_cast<DerivedWithFunctionNoOffset *>(&derivedNoOffset)));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -4));

  sq_poptop(m_vm);

  result = sqb::Bind::BindClass<DerivedWithFunctionWithOffset, BaseWithFunction>(m_vm, -1, _SC("DerivedWithFunctionWithOffset"));
  ASSERT_TRUE(result) << "sqb::Bind::BindClass<DerivedWithFunctionWithOffset, BaseWithFunction>";

  sq_pushstring(m_vm, _SC("DerivedWithFunctionWithOffset"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawget(m_vm, -2));
  ASSERT_TRUE(sqb::Bind::BindClassFunction<DerivedWithFunctionWithOffset>(m_vm, -1, &DerivedWithFunctionWithOffset::DerivedFunction, _SC("DerivedFunction")));

  // bind derivedNoOffset to a slot in the root table
  //
  sq_pushstring(m_vm, _SC("derivedWithOffset"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_createinstance(m_vm, -2));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_setinstanceup(m_vm, -1, static_cast<DerivedWithFunctionWithOffset *>(&derivedWithOffset)));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -4));

  sq_poptop(m_vm);

  sq_poptop(m_vm);

  CompileAndSucceedCall(_SC("baseWithFunction.BaseFunction()"));

  CompileAndSucceedCall(_SC("derivedNoOffset.BaseFunction()"));
  CompileAndSucceedCall(_SC("derivedNoOffset.DerivedFunction()"));

  CompileAndSucceedCall(_SC("derivedWithOffset.BaseFunction()"));
  CompileAndSucceedCall(_SC("derivedWithOffset.DerivedFunction()"));
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindNativeClassFunctionTest, TestInheritance)
{
  ::testing::StrictMock<BaseWithFunction> base;
  ::testing::StrictMock<DerivedWithFunctionNoOffset> derivedNoOffset;
  ::testing::StrictMock<DerivedWithFunctionWithOffset> derivedWithOffset;

  ASSERT_EQ(0, CLASS_OFFSET(BaseWithFunction, DerivedWithFunctionNoOffset));
  ASSERT_NE(0, CLASS_OFFSET(BaseWithFunction, DerivedWithFunctionWithOffset));

  EXPECT_CALL(base, BaseNativeFunction(m_vm))
    .WillOnce(::testing::Return(0));

  EXPECT_CALL(derivedNoOffset, BaseNativeFunction(m_vm))
    .WillOnce(::testing::Return(0));
  EXPECT_CALL(derivedNoOffset, DerivedNativeFunction(m_vm))
    .WillOnce(::testing::Return(0));

  EXPECT_CALL(derivedWithOffset, BaseNativeFunction(m_vm))
    .WillOnce(::testing::Return(0));
  EXPECT_CALL(derivedWithOffset, DerivedNativeFunction(m_vm))
    .WillOnce(::testing::Return(0));

  sq_pushroottable(m_vm);

  bool result = sqb::Bind::BindClass<BaseWithFunction, sqb::NoBaseClass>(m_vm, -1, _SC("BaseWithFunction"));
  ASSERT_TRUE(result) << "sqb::Bind::BindClass<BaseWithFunction, sqb::NoBaseClass>";

  sq_pushstring(m_vm, _SC("BaseWithFunction"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawget(m_vm, -2));
  ASSERT_TRUE(sqb::Bind::BindNativeClassFunction<BaseWithFunction>(m_vm, -1, &BaseWithFunction::BaseNativeFunction, _SC("BaseNativeFunction")));

  // bind base to a slot in the root table
  //
  sq_pushstring(m_vm, _SC("baseWithFunction"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_createinstance(m_vm, -2));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_setinstanceup(m_vm, -1, static_cast<BaseWithFunction *>(&base)));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -4));

  // pop BaseWithFunction class
  //
  sq_poptop(m_vm);

  result = sqb::Bind::BindClass<DerivedWithFunctionNoOffset, BaseWithFunction>(m_vm, -1, _SC("DerivedWithFunctionNoOffset"));
  ASSERT_TRUE(result) << "sqb::Bind::BindClass<DerivedWithFunctionNoOffset, BaseWithFunction>";

  sq_pushstring(m_vm, _SC("DerivedWithFunctionNoOffset"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawget(m_vm, -2));
  ASSERT_TRUE(sqb::Bind::BindNativeClassFunction<DerivedWithFunctionNoOffset>(m_vm, -1, &DerivedWithFunctionNoOffset::DerivedNativeFunction, _SC("DerivedNativeFunction")));

  // bind derivedNoOffset to a slot in the root table
  //
  sq_pushstring(m_vm, _SC("derivedNoOffset"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_createinstance(m_vm, -2));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_setinstanceup(m_vm, -1, static_cast<DerivedWithFunctionNoOffset *>(&derivedNoOffset)));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -4));

  sq_poptop(m_vm);

  result = sqb::Bind::BindClass<DerivedWithFunctionWithOffset, BaseWithFunction>(m_vm, -1, _SC("DerivedWithFunctionWithOffset"));
  ASSERT_TRUE(result) << "sqb::Bind::BindClass<DerivedWithFunctionWithOffset, BaseWithFunction>";

  sq_pushstring(m_vm, _SC("DerivedWithFunctionWithOffset"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawget(m_vm, -2));
  ASSERT_TRUE(sqb::Bind::BindNativeClassFunction<DerivedWithFunctionWithOffset>(m_vm, -1, &DerivedWithFunctionWithOffset::DerivedNativeFunction, _SC("DerivedNativeFunction")));

  // bind derivedNoOffset to a slot in the root table
  //
  sq_pushstring(m_vm, _SC("derivedWithOffset"), -1);
  ASSERT_SQ_SUCCEEDED(m_vm, sq_createinstance(m_vm, -2));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_setinstanceup(m_vm, -1, static_cast<DerivedWithFunctionWithOffset *>(&derivedWithOffset)));
  ASSERT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -4));

  sq_poptop(m_vm);

  sq_poptop(m_vm);

  CompileAndSucceedCall(_SC("baseWithFunction.BaseNativeFunction()"));

  CompileAndSucceedCall(_SC("derivedNoOffset.BaseNativeFunction()"));
  CompileAndSucceedCall(_SC("derivedNoOffset.DerivedNativeFunction()"));

  CompileAndSucceedCall(_SC("derivedWithOffset.BaseNativeFunction()"));
  CompileAndSucceedCall(_SC("derivedWithOffset.DerivedNativeFunction()"));
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindSingletonFunctionTest, TestInheritance)
{
  ::testing::StrictMock<BaseWithFunction> base;
  ::testing::StrictMock<DerivedWithFunctionNoOffset> derivedNoOffset;
  ::testing::StrictMock<DerivedWithFunctionWithOffset> derivedWithOffset;

  ASSERT_EQ(0, CLASS_OFFSET(BaseWithFunction, DerivedWithFunctionNoOffset));
  ASSERT_NE(0, CLASS_OFFSET(BaseWithFunction, DerivedWithFunctionWithOffset));

  EXPECT_CALL(base, BaseFunction())
    .Times(1);
  EXPECT_CALL(derivedNoOffset, DerivedFunction())
    .Times(1);
  EXPECT_CALL(derivedWithOffset, DerivedFunction())
    .Times(1);

  sq_pushroottable(m_vm);

  ASSERT_TRUE(sqb::Bind::BindSingletonFunction<BaseWithFunction>(m_vm, -1, &base, &BaseWithFunction::BaseFunction, _SC("BaseFunction")));
  ASSERT_TRUE(sqb::Bind::BindSingletonFunction<DerivedWithFunctionNoOffset>(m_vm, -1, &derivedNoOffset, &DerivedWithFunctionNoOffset::DerivedFunction, _SC("DerivedFunctionNoOffset")));
  ASSERT_TRUE(sqb::Bind::BindSingletonFunction<DerivedWithFunctionWithOffset>(m_vm, -1, &derivedWithOffset, &DerivedWithFunctionWithOffset::DerivedFunction, _SC("DerivedFunctionWithOffset")));

  sq_poptop(m_vm);

  CompileAndSucceedCall(_SC("BaseFunction()"));
  CompileAndSucceedCall(_SC("DerivedFunctionNoOffset()"));
  CompileAndSucceedCall(_SC("DerivedFunctionWithOffset()"));
}

//----------------------------------------------------------------------------------------------------------------------
TEST_F(BindNativeSingletonFunctionTest, TestInheritance)
{
  ::testing::StrictMock<BaseWithFunction> base;
  ::testing::StrictMock<DerivedWithFunctionNoOffset> derivedNoOffset;
  ::testing::StrictMock<DerivedWithFunctionWithOffset> derivedWithOffset;

  ASSERT_EQ(0, CLASS_OFFSET(BaseWithFunction, DerivedWithFunctionNoOffset));
  ASSERT_NE(0, CLASS_OFFSET(BaseWithFunction, DerivedWithFunctionWithOffset));

  EXPECT_CALL(base, BaseNativeFunction(m_vm))
    .WillOnce(::testing::Return(0));
  EXPECT_CALL(derivedNoOffset, DerivedNativeFunction(m_vm))
    .WillOnce(::testing::Return(0));
  EXPECT_CALL(derivedWithOffset, DerivedNativeFunction(m_vm))
    .WillOnce(::testing::Return(0));

  sq_pushroottable(m_vm);

  ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction<BaseWithFunction>(m_vm, -1, &base, &BaseWithFunction::BaseNativeFunction, _SC("BaseNativeFunction")));
  ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction<DerivedWithFunctionNoOffset>(m_vm, -1, &derivedNoOffset, &DerivedWithFunctionNoOffset::DerivedNativeFunction, _SC("DerivedNativeFunctionNoOffset")));
  ASSERT_TRUE(sqb::Bind::BindNativeSingletonFunction<DerivedWithFunctionWithOffset>(m_vm, -1, &derivedWithOffset, &DerivedWithFunctionWithOffset::DerivedNativeFunction, _SC("DerivedNativeFunctionWithOffset")));

  sq_poptop(m_vm);

  CompileAndSucceedCall(_SC("BaseNativeFunction()"));
  CompileAndSucceedCall(_SC("DerivedNativeFunctionNoOffset()"));
  CompileAndSucceedCall(_SC("DerivedNativeFunctionWithOffset()"));
}
